﻿using System;
using System.Data.SqlTypes;
using System.IO;
using GeoAPI.Geometries;
using GeoAPI.Operations.Buffer;
using Microsoft.SqlServer.Server;

namespace MsSqlSpatial.UserDefinedTypes
{
    [Serializable]
    [SqlUserDefinedType(
        Format.UserDefined,
        IsByteOrdered = true,
        IsFixedLength = false,
        MaxByteSize = -1, //for sql2008 you can set this to -1 for sql 2005 set to 8000
        Name = "MsSqlSpatialGeometry")]
    public class MsSqlSpatialGeometry
        : IBinarySerialize, INullable
    {
        [NonSerialized]
        private MsSqlSpatialEnvelope _envelope;
        [NonSerialized]
        private IGeometry Geometry;


        public MsSqlSpatialGeometry()
        {
        }

        public MsSqlSpatialGeometry(SqlBytes wkb, SqlInt32 srid)
        {
            if (!wkb.IsNull)
                Geometry = Global.Services[srid].WkbReader.Read(wkb.Value);
        }

        public MsSqlSpatialGeometry(SqlBytes wkb)
        {
            if (!wkb.IsNull)
                Geometry = Global.Services.GeometryFactory.WkbReader.Read(wkb.Value);
        }

        public MsSqlSpatialGeometry(SqlString wkt)
        {
            if (!wkt.IsNull)
                Geometry = Global.Services.GeometryFactory.WktReader.Read(wkt.Value);
        }

        public MsSqlSpatialGeometry(SqlString wkt, SqlInt32 srid)
        {
            if (!wkt.IsNull)
                Geometry = Global.Services[srid].WktReader.Read(wkt.Value);
        }

        public static MsSqlSpatialGeometry Null
        {
            get { return new MsSqlSpatialGeometry(); }
        }


        public SqlString GeometryTypeName
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull) return new SqlString(Geometry.GeometryTypeName);
                return null;
            }
        }


        public SqlBoolean IsRectangle
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull) return new SqlBoolean(Geometry.IsRectangle);
                return false;
            }
        }


        public SqlBoolean IsSimple
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull) return new SqlBoolean(Geometry.IsSimple);
                return false;
            }
        }


        public SqlBoolean IsValid
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull) return new SqlBoolean(Geometry.IsValid);
                return false;
            }
        }


        public SqlInt32 PointCount
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull) return new SqlInt32(Geometry.PointCount);
                return 0;
            }
        }


        public SqlInt32 Srid
        {
            [SqlMethod(IsDeterministic = true, IsMutator = false)]
            get
            {
                if (!IsNull)
                {
                    int? srid = Global.ParseSridString(Geometry.Srid);
                    if (!srid.HasValue)
                        return new SqlInt32();
                    return new SqlInt32(srid.Value);
                }
                return new SqlInt32();
            }
        }


        public MsSqlSpatialGeometry Boundary
        {
            [SqlMethod(IsPrecise = true, IsDeterministic = true, IsMutator = false)]
            get
            {
                var boundary = new MsSqlSpatialGeometry();
                if (!IsNull) boundary.Geometry = Geometry.Boundary;

                return boundary;
            }
        }

        public MsSqlSpatialEnvelope Envelope
        {
            [SqlMethod(IsPrecise = true, IsDeterministic = true, IsMutator = false)]
            get
            {
                if (_envelope.IsNull && !IsNull)
                    _envelope = new MsSqlSpatialEnvelope((IExtents2D)Geometry.Extents);
                return _envelope;
            }
        }

        #region IBinarySerialize Members

        public void Read(BinaryReader r)
        {
            Geometry = Global.Services.GeometryFactory.WkbReader.Read(r);
        }

        public void Write(BinaryWriter w)
        {
            if (Geometry != null)
                w.Write(Geometry.AsBinary());
        }

        #endregion

        #region INullable Members

        public bool IsNull
        {
            get { return ReferenceEquals(null, Geometry); }
        }

        #endregion

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public static MsSqlSpatialGeometry STGeomFromWKB(SqlBytes wkb, SqlInt32 srid)
        {
            return new MsSqlSpatialGeometry(wkb, srid);
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public static MsSqlSpatialGeometry STGeomFromWKT(SqlString wkt, SqlInt32 srid)
        {
            return new MsSqlSpatialGeometry(wkt, srid);
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public static MsSqlSpatialGeometry Parse(SqlString wkt)
        {
            return new MsSqlSpatialGeometry(wkt);
        }

        public override string ToString()
        {
            if (!IsNull)
                return Geometry.AsText();
            return "";
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBytes AsBinary()
        {
            if (!IsNull) return new SqlBytes(Geometry.AsBinary());
            return null;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlString AsText()
        {
            if (!IsNull) return new SqlString(Geometry.AsText());
            return null;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Clone()
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Clone();
            return g;
        }


        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Buffer(double distance, int quadrantSegments, BufferStyle endCapStyle)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Buffer(distance, quadrantSegments, endCapStyle);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Buffer(double distance, BufferStyle endCapStyle)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Buffer(distance, endCapStyle);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Buffer(double distance, int quadrantSegments)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Buffer(distance, quadrantSegments);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Buffer(double distance)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Buffer(distance);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry ConvexHull()
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.ConvexHull();
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Difference(MsSqlSpatialGeometry other)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Difference(other.Geometry);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public double Distance(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return Geometry.Distance(g.Geometry);
            return double.NaN;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Intersection(MsSqlSpatialGeometry other)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Intersection(other.Geometry);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry SymmetricDifference(MsSqlSpatialGeometry other)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.SymmetricDifference(other.Geometry);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public MsSqlSpatialGeometry Union(MsSqlSpatialGeometry other)
        {
            var g = new MsSqlSpatialGeometry();
            if (!IsNull) g.Geometry = Geometry.Union(other.Geometry);
            return g;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Contains(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Contains(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean CoveredBy(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.CoveredBy(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Covers(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Covers(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Crosses(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Crosses(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Disjoint(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Disjoint(g.Geometry));
            return true;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Equals(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Equals(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean EqualsExact(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.EqualsExact(g.Geometry));
            return false;
        }


        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Intersects(MsSqlSpatialGeometry g)
        {
            if (!IsNull)
            {
                if (Envelope.Intersects(g.Envelope))
                    return new SqlBoolean(Geometry.Intersects(g.Geometry));
            }
            return false;
        }


        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean IsWithinDistance(MsSqlSpatialGeometry g, double distance)
        {
            if (!IsNull) return new SqlBoolean(Geometry.IsWithinDistance(g.Geometry, distance));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Overlaps(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Overlaps(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlString RelatePattern(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlString(Geometry.Relate(g.Geometry).ToString());
            return null;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Relate(MsSqlSpatialGeometry g, string intersectionPattern)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Relate(g.Geometry, intersectionPattern));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Touches(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Touches(g.Geometry));
            return false;
        }

        [SqlMethod(
            DataAccess = DataAccessKind.None,
            OnNullCall = false,
            SystemDataAccess = SystemDataAccessKind.None,
            IsMutator = false,
            IsPrecise = true,
            IsDeterministic = true)]
        public SqlBoolean Within(MsSqlSpatialGeometry g)
        {
            if (!IsNull) return new SqlBoolean(Geometry.Within(g.Geometry));
            return false;
        }
    }
}